package mil.af.amc.adpetracker.service;

import mil.af.amc.adpetracker.dao.*;
import mil.af.amc.adpetracker.exceptions.DateFormatException;
import mil.af.amc.adpetracker.exceptions.EquipmentItemNotFoundException;
import mil.af.amc.adpetracker.model.*;
import mil.af.amc.adpetracker.service.exceptions.AdpeAccountNotFoundException;
import mil.af.amc.adpetracker.service.exceptions.ItemStatusNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSendException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * Implementation of EquipmentItemService
 *
 * @author TSgt Jason Ferguson
 */
@Service("equipmentItemService")
public class EquipmentItemServiceImpl implements EquipmentItemService {

    private static final Log log = LogFactory.getLog(EquipmentItemServiceImpl.class);

    public EquipmentItemDao equipmentItemDao;

    @Autowired
    public void setEquipmentItemDao(EquipmentItemDao equipmentItemDao) {
        this.equipmentItemDao = equipmentItemDao;
    }


    private AdpeAccountDao adpeAccountDao;

    @Autowired
    public void setAdpeAccountDao(AdpeAccountDao adpeAccountDao) {
        this.adpeAccountDao = adpeAccountDao;
    }

    private EquipmentTypeDao equipmentTypeDao;

    @Autowired
    public void setEquipmentTypeDao(EquipmentTypeDao equipmentTypeDao) {
        this.equipmentTypeDao = equipmentTypeDao;
    }

    private ItemModelService itemModelService;

    @Autowired
    public void setItemModelService(ItemModelService itemModelService) {
        this.itemModelService = itemModelService;
    }

    private MailSender mailSender;

    @Autowired
    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    private MaintenanceEntryDao maintenanceDao;

    @Autowired
    public void setMaintenanceDao(MaintenanceEntryDao maintenanceDao) {
        this.maintenanceDao = maintenanceDao;
    }

    private ManufacturerDao manufacturerDao;

    @Autowired
    public void setManufacturerDao(ManufacturerDao manufacturerDao) {
        this.manufacturerDao = manufacturerDao;
    }

    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public EquipmentItem createItem(EquipmentItem item) {
        throw new UnsupportedOperationException();
    }

    // TODO: Make editItem() able to change the item type
    public void editItem(Map itemMap) throws EquipmentItemNotFoundException {

        int id = (Integer) itemMap.get("itemId");
        EquipmentItem ei = equipmentItemDao.getById(id);

        if (ei == null) {
            throw new EquipmentItemNotFoundException();
        }
        // IPMS
        ei.setIpms((String) itemMap.get("ipmsNumber"));
        // SERIAL
        ei.setSerial((String) itemMap.get("serialNumber"));

        // Manufacturer
        int manufacturerId = (Integer) itemMap.get("manufacturerId");


        // Model from ID
        int modelId = (Integer) itemMap.get("modelId");
        ItemModel model = itemModelService.getById(modelId);
        ei.setItemModel(model);

        // Description
        ei.setDescription((String) itemMap.get("description"));

        String purchaseDate = ((String) itemMap.get("purchaseDate")).toUpperCase();

        // purchase date
        String dateFormatPattern = "dd-MMM-yyyy";
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormatPattern);

        try {
            Date pDate = sdf.parse(purchaseDate);
            ei.setPurchaseDate(pDate);
        } catch (ParseException e) {
            log.debug("Bad date input: " + purchaseDate, e);
            System.out.println("Bad date input: " + purchaseDate);
        }

//        try {
//            String purchaseDateString = (String) itemMap.get("purchaseDate");
//            Date purchaseDate = new SimpleDateFormat("MM/dd/yyyy").parse(purchaseDateString);
//            ei.setPurchaseDate(new java.sql.Date(purchaseDate.getTime()));
//        } catch (ParseException e) {
//            //throw new DateFormatException("Badly formatted purchase date (use YYYY-MM-DD)");
//        }
        // purchase cost
        try {
            String purchaseCost = (String) itemMap.get("purchaseCost");
            ei.setOriginalCost(Double.parseDouble(purchaseCost));
        } catch (NumberFormatException e) {
            ei.setOriginalCost(0.0);
        }
        // adpe account from id
        int adpeAccountId = (Integer) itemMap.get("adpeAccountId");
        AdpeAccount acct = adpeAccountDao.getById(adpeAccountId);
        ei.setAccount(acct);

        ei.setLocation((String) itemMap.get("location"));

        // Item Condition
        String condition = (String) itemMap.get("itemCondition");
        int conditionId = Integer.parseInt(condition);
        ei.setItemCondition(Condition.values()[conditionId]);

        int statusId = (Integer) itemMap.get("itemStatusId");
        ei.setItemStatus(Status.values()[statusId]);
        equipmentItemDao.saveOrUpdate(ei);
    }

    public EquipmentItem getBySerialNumber(String serial) {

        return equipmentItemDao.getBySerialNumber(serial);

    }

    public EquipmentItem getBySerialAndManufacturer(String serial, int manufacturerId) {

        Manufacturer manu = manufacturerDao.getById(manufacturerId);

        return equipmentItemDao.getBySerialAndManufacturer(serial, manu);

    }

    public EquipmentItem getById(int id) {

        return equipmentItemDao.getById(id);

    }

    public List<EquipmentItem> createMultipleItems(Map itemInfo) {
        throw new UnsupportedOperationException();
    }

    public int getCountByAccount(int accountId) {

        AdpeAccount account = adpeAccountDao.getById(accountId);

        return equipmentItemDao.getCountByAccount(account);
    }

    public void delete(EquipmentItem item) {
        throw new UnsupportedOperationException();
    }

    public void save(EquipmentItem item) {

        equipmentItemDao.save(item);

    }

    @SuppressWarnings({"ForLoopReplaceableByForEach"})
    public void saveItems(Map itemMap) {

        int adpeAccountId = (Integer) itemMap.get("adpeAccount");
        int t = (Integer) itemMap.get("equipmentType");
        EquipmentType type = equipmentTypeDao.getById(t);

        Integer[] manufacturer = (Integer[]) itemMap.get("manufacturer");
        Integer[] model = (Integer[]) itemMap.get("model");
        String[] serial = (String[]) itemMap.get("serialNumber");
        String[] description = (String[]) itemMap.get("description");
        Double[] itemCost = (Double[]) itemMap.get("itemCost");
        String[] purchaseDate = (String[]) itemMap.get("purchaseDate");

        for (int i = 0; i < manufacturer.length; i++) {

            // If we have a blank, do NOT insert it into the database!
            if (model[i] > -1 && description[i] != null && manufacturer[i] > -1) {
                EquipmentItem item = new EquipmentItem();
                item.setAccount(adpeAccountDao.getById(adpeAccountId));
                item.setItemModel(itemModelService.getById(model[i]));
                item.setEquipmentType(type);
                item.setDescription(description[i]);
                item.setSerial(serial[i]);
                item.setOriginalCost(itemCost[i]);

                String dateFormatPattern = "dd-MMM-yyyy";
                SimpleDateFormat sdf = new SimpleDateFormat(dateFormatPattern);

                try {
                    Date pDate = sdf.parse(purchaseDate[i]);
                    item.setPurchaseDate(pDate);
                } catch (ParseException e) {
                    log.debug("Bad date input: " + purchaseDate[i], e);
                    System.out.println("Bad date input: " + purchaseDate[i]);
                }
                //item.setPurchaseDate(purchaseDate[i]);

                // By default, these things are servicable and active
                item.setItemCondition(Condition.A);
                item.setItemStatus(Status.Active);

                MaintenanceEntry maint = createMaintenanceEntry(item, new Date(), "Item entered in tracker.");

                item.getMaintenanceEntries().add(maint);

                equipmentItemDao.saveOrUpdate(item);
            }
        }
    }

    public List<EquipmentItem> getAll() {

        return equipmentItemDao.getAll();

    }

    public List<EquipmentItem> getAllByAccount(int accountId) {

        AdpeAccount acct = adpeAccountDao.getById(accountId);

        return equipmentItemDao.getAllByAccount(acct);

    }

    public List<EquipmentItem> getAllActiveByAccount(int accountId) {
        throw new UnsupportedOperationException();
    }

    public List<EquipmentItem> getAllByItemModel(int itemModelId) {

        ItemModel model = itemModelService.getById(itemModelId);
        return getAllByModel(model);
    }

    public List<EquipmentItem> getAllByModel(ItemModel model) {

        return equipmentItemDao.getAllByModel(model);

    }

    public List<EquipmentItem> getAllActiveByModel(ItemModel model) {

        return equipmentItemDao.getAllByModel(model);

    }

    public List<EquipmentItem> getAllCurrentByAccount(int accountId) {

        AdpeAccount acct = adpeAccountDao.getById(accountId);

        return equipmentItemDao.getAllCurrentByAccount(acct);

    }

    public List<EquipmentItem> createMultipleItems(byte[] file)
            throws AdpeAccountNotFoundException, ItemStatusNotFoundException, DateFormatException {

        throw new UnsupportedOperationException("This method has not been updated to deal with the new ItemModel class");

    }

    public void assignItem(Map itemMap) throws ServiceException {

        int userId = (Integer) itemMap.get("userId");
        User user = userDao.getById(userId);

        EquipmentItem item;
        if (itemMap.containsKey("serial") && itemMap.get("serial") != null) {
            item = equipmentItemDao.getById((Integer) itemMap.get("serial"));
        } else {
            item = equipmentItemDao.getById((Integer) itemMap.get("itemId"));
        }

        if (item.getAssignedUser() != null) {

            // I don't want this exception passed back up to the action in this instance.
            try {
                unassignItem(item.getId());
            } catch (ServiceException e) {

            }
        }

        item.setAssignedUser(user);
        user.getAssignedEquipment().add(item);

        StringBuilder hb = new StringBuilder();
        hb.append("<p>You may need to click Restore Line Breaks to properly view this message.</p>\r\n");
        hb.append("<p>The following item has been assigned to you:</p>\r\n");
        hb.append("<p>").append(item.getDescription()).append(" (").append(item.getItemModel().getManufacturer()
                .getName()).append(", ").append("Serial Number: ").append(item.getSerial()).append(") </p>\r\n");
        hb.append("<p>&nbsp;</p>\r\n");
        hb.append("<p>The following items are still assigned to you: </p>\r\n");
        hb.append("<p>The following items are currently assigned to you:</p>");
        hb.append("<table style=\"width:100%\" />\r\n");
        hb.append("<thead>\r\n");
        hb.append("<th>Serial Number</th>\r\n").append("<th>Manufacturer</th>").append("<th>Model</th>\r\n").append("<th>Description</th>\r\n").append("<th>Type</th>\r\n");
        hb.append("</thead>\r\n");
        hb.append("<tbody>\r\n");
        for (EquipmentItem item2 : user.getAssignedEquipment()) {
            hb.append("<tr>\r\n");
            hb.append("<td>").append(item2.getSerial()).append("</td>\r\n");
            hb.append("<td>").append(item2.getItemModel().getManufacturer().getName()).append("</td>\r\n");
            hb.append("<td>").append(item2.getItemModel().getName()).append("</td>\r\n");
            hb.append("<td>").append(item2.getDescription()).append("</td>\r\n");
            hb.append("<td>").append(item2.getItemModel().getType().getName()).append("</td>\r\n");
        }
        hb.append("</tbody>\r\n");
        hb.append("</table>\r\n");
        hb.append("<p>&nbsp;</p>");
        hb.append("<p>If this information is in error, please contact your ADPE Custodian to have it corrected.</p>\r\n");

        try {
            MimeMessage msg = ((JavaMailSenderImpl) mailSender).createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(msg);
            helper.setTo(user.getEmail());
            helper.setFrom("ADPETRACK-NOREPLY@scott.af.mil");
            helper.setSubject("Currently Assigned ADPE Notification");
            helper.setText(hb.toString(), true);

            ((JavaMailSenderImpl) mailSender).send(msg);
        } catch (MessagingException e) {
            log.debug("Failed to send status message to " + user.getDisplayName());
            throw new ServiceException("Failed to send status message. ", e);
        } catch (MailSendException e) {
            log.debug("Failed to send status message to " + user.getDisplayName());
            throw new ServiceException("Failed to send status message. ", e);
        }

        MaintenanceEntry maint = createMaintenanceEntry(item, new Date(),
                "Item assigned to ".concat(item.getAssignedUser().getDisplayName()));

        maintenanceDao.save(maint);

        item.getMaintenanceEntries().add(maint);
        equipmentItemDao.save(item);

    }

    protected SimpleMailMessage createNotificationMessage(String email, String subject, String text) {

        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setTo(email);
        msg.setSubject(subject);
        msg.setText(text);

        return msg;
    }

    protected MaintenanceEntry createMaintenanceEntry(EquipmentItem item, Date date, String text) {

        MaintenanceEntry maint = new MaintenanceEntry();
        maint.setItem(item);
        maint.setEntryDate(date);
        maint.setEntryText(text);

        return maint;
    }


    public void unassignItem(int equipmentItemId) throws ServiceException {

        EquipmentItem item = equipmentItemDao.getById(equipmentItemId);
        unassignItem(item);
    }

    public void unassignItem(EquipmentItem item) throws ServiceException {

        //EquipmentItem item = equipmentItemDao.getById(itemId);
        User user = item.getAssignedUser();

        if (user == null) {
            throw new ServiceException("error.equipmentitem.notassigned", "Item is no longer assigned to the user");
        }

        item.setAssignedUser(null);
        equipmentItemDao.saveOrUpdate(item);

        user.getAssignedEquipment().remove(item);
        userDao.saveOrUpdate(user);

        StringBuilder hb = new StringBuilder();
        hb.append("<p>The following item is no longer assigned to you:</p>");
        hb.append("<p>").append(item.getDescription()).append("(").append(item.getItemModel().getManufacturer()
                .getName()).append(", Serial Number: ").append(item.getSerial()).append("</p>");
        hb.append("<p>&nbsp;</p>");
        hb.append("<p>The following items are currently assigned to you:</p>");
        hb.append("<table style=\"width:100%\" />\r\n");
        hb.append("<thead>\r\n");
        hb.append("<th>Serial Number</th>\r\n").append("<th>Manufacturer</th>").append("<th>Model</th>\r\n").append("<th>Description</th>\r\n").append("<th>Type</th>\r\n");
        hb.append("</thead>\r\n");
        hb.append("<tbody>\r\n");
        for (EquipmentItem item2 : user.getAssignedEquipment()) {
            hb.append("<tr>\r\n");
            hb.append("<td>").append(item2.getSerial()).append("</td>\r\n");
            hb.append("<td>").append(item2.getItemModel().getManufacturer().getName()).append("</td>\r\n");
            hb.append("<td>").append(item2.getItemModel().getName()).append("</td>\r\n");
            hb.append("<td>").append(item2.getDescription()).append("</td>\r\n");
            hb.append("<td>").append(item2.getItemModel().getType().getName()).append("</td>\r\n");
        }
        hb.append("</tbody>\r\n");
        hb.append("</table>\r\n");
        hb.append("<p>&nbsp;</p>");
        hb.append("<p>If this information is in error, please contact your ADPE Custodian to have it corrected.</p>\r\n");

        try {
            MimeMessage msg = ((JavaMailSenderImpl) mailSender).createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(msg);
            helper.setTo(user.getEmail());
            helper.setFrom("ADPETRACK-NOREPLY@scott.af.mil");
            helper.setSubject("Currently Assigned ADPE Notification");
            helper.setText(hb.toString(), true);

            ((JavaMailSenderImpl) mailSender).send(msg);
        } catch (MessagingException e) {
            log.debug("Failed to send status message to " + user.getDisplayName());
            throw new ServiceException("Failed to send status message. ", e);
        } catch (MailSendException e) {
            log.debug("Failed to send status message to " + user.getDisplayName());
            throw new ServiceException("Failed to send status message. ", e);
        }
        // MaintenanceEntry maint = MaintenanceEntryFactory.createLogEntry();
        MaintenanceEntry maint = new MaintenanceEntry();
        maint.setItem(item);
        maint.setEntryDate(new Date());
        maint.setEntryText("Item unassigned from ".concat(user.getDisplayName()));

        item.getMaintenanceEntries().add(maint);
        equipmentItemDao.save(item);

        maintenanceDao.save(maint);
    }

    public void sendStatusEmail(int userId) throws ServiceException {

        User user = userDao.getById(userId);
        sendStatusEmail(user);
    }

    // TODO: Add Hand Receipt listing to sendStatusEmail
    @SuppressWarnings({"StringConcatenationInsideStringBufferAppend"})
    protected void sendStatusEmail(User user) throws ServiceException {

        StringBuilder hb = new StringBuilder();

        hb.append("<p>You may need to select View As HTML to view this message correctly.</p>");
        hb.append("<p>The following list shows all items currently assigned to you:</p>\r\n");
        hb.append("<table style=\"width:100%\">\r\n");
        hb.append("<thead>\r\n");
        hb.append("<tr>\r\n");
        hb.append("<th>Serial</th>\r\n");
        hb.append("<th>Manufacturer</th>\r\n");
        hb.append("<th>Model</th>\r\n");
        hb.append("<th>Type</th>\r\n");
        hb.append("</tr>");

        for (EquipmentItem item : user.getAssignedEquipment()) {

            hb.append("<tr>\r\n");
            hb.append("<td>" + item.getSerial() + "</td>\r\n");
            hb.append("<td>" + item.getItemModel().getManufacturer().getName() + "</td>\r\n");
            hb.append("<td>" + item.getItemModel().getName() + "</td>\r\n");
            hb.append("<td>" + item.getDescription() + "</td>\r\n");
            hb.append("<td>" + item.getItemModel().getType().getName() + "</td>\r\n");
            hb.append("</tr>\r\n");

        }
        hb.append("</table>\r\n");
        hb.append("<p>If this information is in error, please contact your equipment custodian to have it " +
                "corrected.</p>\r\n");

        try {
            MimeMessage msg = ((JavaMailSenderImpl) mailSender).createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(msg);
            helper.setTo(user.getEmail());
            helper.setFrom("ADPETRACK-NOREPLY@scott.af.mil");
            helper.setSubject("Currently Assigned ADPE Notification");
            helper.setText(hb.toString(), true);

            ((JavaMailSenderImpl) mailSender).send(msg);

        } catch (MessagingException e) {

            log.debug("Failed to send status message to " + user.getDisplayName());
            throw new ServiceException("Failed to send status message. ", e);
        } catch (MailSendException e) {
            log.debug("Failed to send status message to " + user.getDisplayName());
            throw new ServiceException("Failed to send status message. ", e);
        }

        log.debug("Sent status email to " + user.getDisplayName() + ". Total of "
                + user.getAssignedEquipment().size() + " items assigned.");

    }

    public List<EquipmentItem> getByItemType(int equipmentTypeId) {

        EquipmentType type = equipmentTypeDao.getById(equipmentTypeId);
        return getByItemType(type);
    }

    public List<EquipmentItem> getByItemType(EquipmentType itemType) {

        return equipmentItemDao.getByItemType(itemType);

    }

    public List<EquipmentItem> getByManufacturer(int manufacturerId) {
        throw new UnsupportedOperationException();
    }

    public List<EquipmentItem> getByManufacturer(Manufacturer manu) {
        throw new UnsupportedOperationException();
    }

    public List<EquipmentItem> getByAdpeAccountAndEquipmentTypeAndManufacturer(int adpeAccountId,
                                                                               int equipmentTypeId,
                                                                               int manufacturerId) {

        AdpeAccount acct = adpeAccountDao.getById(adpeAccountId);
        EquipmentType type = equipmentTypeDao.getById(equipmentTypeId);
        Manufacturer manu = manufacturerDao.getById(manufacturerId);

        return getByAdpeAccountAndEquipmentTypeAndManufacturer(acct, type, manu);

    }

    public List<EquipmentItem> getByAdpeAccountAndEquipmentTypeAndManufacturer(AdpeAccount acct,
                                                                               EquipmentType type,
                                                                               Manufacturer manu) {

        EquipmentItem item = new EquipmentItem();
        if (acct != null) {
            item.setAccount(acct);
        }
        item.setItemModel(new ItemModel());
        if (type != null) {
            item.getItemModel().setType(type);
        }
        if (manu != null) {
            item.getItemModel().setManufacturer(manu);
        }

        //return equipmentItemDao.getItemsByQbe(item);
        return equipmentItemDao.getByAdpeAccountAndEquipmentTypeAndManufacturer(acct, type, manu);
    }

    public void updateLocation(EquipmentItem item, String location) {

        item.setLocation(location);

        User user = item.getAssignedUser();
        if (user != null) {

            try {
                unassignItem(item.getId());
            } catch (ServiceException e) {

            }
        }

    }

    public void updateInventoryDate(int itemId, Date date) {

        EquipmentItem item = equipmentItemDao.getById(itemId);
        item.setLastInventoryDate(date);

    }

    public void transferItem(String serialNumber, int accountId) throws ServiceException {


        EquipmentItem item = equipmentItemDao.getBySerialNumber(serialNumber);

        if (item==null) {
            throw new ServiceException("messages.equipmentitem.notfound", "Unable to locate item by given serial number");

        }

        AdpeAccount acct = adpeAccountDao.getById(accountId);
        item.setAccount(acct);

        adpeAccountDao.save(acct);
        equipmentItemDao.save(item);

    }

    public void transferItems(Integer[] pkgItemId, int accountId) throws ServiceException {

        AdpeAccount acct = adpeAccountDao.getById(accountId);

        if (acct == null) {
            throw new ServiceException("messages.adpeaccount.notfound", "Unable to locate ADPE Account");
        }

        for (int equipmentItemId : pkgItemId) {

            EquipmentItem item = equipmentItemDao.getById(equipmentItemId);

            if (item == null) {
                throw new ServiceException("messages.equipmentitem.notfound", "Unable to locate equipment item #" + equipmentItemId);
            }

            String oldAdpeAccountName = item.getAccount().getName();  // Get the old name so I can use it in a MaintEntry
            item.setAccount(acct);

            MaintenanceEntry maint = new MaintenanceEntry();
            maint.setEntryDate(new Date());
            maint.setItem(item);
            maint.setEntryText("Item transferred from ADPE Account " + oldAdpeAccountName);

            maintenanceDao.save(maint);

            item.getMaintenanceEntries().add(maint);
            equipmentItemDao.save(item);

        }
    }
}


